package org.helsoft.mojo;

import java.io.File;
import java.io.FileFilter;
import java.io.FilenameFilter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

import org.apache.maven.artifact.Artifact;
import org.apache.maven.model.Dependency;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.MojoFailureException;
import org.apache.tools.ant.taskdefs.Ant;
import org.codehaus.plexus.util.FileUtils;
import org.helsoft.mojo.api.Bundle;
import org.helsoft.mojo.api.BundleFileFilter;
import org.helsoft.mojo.api.ExtensionFilter;
import org.helsoft.mojo.api.NameFilter
import org.helsoft.mojo.api.PDEMojo;
import org.helsoft.mojo.api.PluginDescriptor;
import org.helsoft.mojo.api.Version;

/**
 * 
 * @author hesteguet
 * 
 * @goal compile
 */
public class CompileMojo extends PDEMojo {
	/**
	 * Eclipse Location.
	 * 
	 * @parameter expression="${eclipseLocation}"
	 * @required
	 */
	private String eclipseLocation;

	/**
	 * The location of the workspace where this plugin is located.
	 * 
	 * @parameter expression="${workspaceLocation}"
	 *            default-value="${project.basedir}/.."
	 * @required
	 */
	private String workspaceLocation;

	/**
	 * @parameter expression="${pde.generatePom}" default-value="false"
	 */
	protected Boolean updatePOM;

	/**
	 * 
	 */
	private final List<File> resolvedBundles = new ArrayList<File>();

	@Override
	public void execute() throws MojoExecutionException, MojoFailureException {
		if (eclipseLocation == null) {
			throw new MojoExecutionException(
					"An eclipse location should be specified");
		}

		File eclipseDir = new File(eclipseLocation);
		if (!eclipseDir.isDirectory()) {
			throw new MojoExecutionException("'" + eclipseDir
					+ "' it's not a valid eclipse installation directory");
		}

		File pluginsDir = new File(eclipseLocation, "plugins");
		if (!pluginsDir.isDirectory()) {
			throw new MojoExecutionException("'" + eclipseDir
					+ "' it's not a valid eclipse installation directory : "
					+ "missing plugins directory");
		}

		createAllDependencies(getPluginDescriptor(), pluginsDir);

		if (updatePOM) {
			try {
				writeOriginalPOM(pdePOMFile);
			} catch (IOException e) {
				fail("Unable to generate the PDE pom", e);
			}
		}
		
//		ant.exec(executable: 'mvn', arg:'-f ' + pdePOMFile.absolutePath + ' compile')
	}

	private File resolvePluginFrom(File pluginDir, Bundle bundle)
			throws MojoExecutionException, MojoFailureException {
		File result = null;

		File[] files = pluginDir.listFiles(new BundleFileFilter(bundle));
		if (files.length == 0) {
			// We take any version of the bundle
			files = pluginDir.listFiles(new BundleFileFilter(bundle, true));
		}

		if (files.length > 0) {
			result = files[files.length - 1];
		}

		return result;
	}

	private File resolvePluginFromWorkspace(Bundle bundle) {
		File result = null;

		File dir = workspaceLocation == null ? null : new File(
				workspaceLocation);
		if (dir != null && dir.isDirectory()
				&& new File(dir, ".metadata").isDirectory()) {
			final String name = bundle.getFullName();
			if (name != null) {
				File[] files = new File(workspaceLocation).listFiles(new NameFilter(name));

				if (files.length == 1) {
					// The first step is to synchronize the manifest with the
					// bundle infos
					PluginDescriptor desc = null;
					try {
						desc = PluginDescriptor.fromFile(files[0]);
						// We try to replace the version of the bundle with the
						// one of the plugin
						String bundleVersion = desc.getBundleVersion();
						if (bundleVersion != null
								&& !bundleVersion.trim().isEmpty()) {
							bundle.setVersion(bundleVersion);
						}
					} catch (IOException e) {
						// Ignore
					}
				}

			}
		}

		return result;
	}

	private File[] resolvePluginDependencies(File pluginDir, File pluginLocation)
			throws MojoExecutionException, MojoFailureException {
		File[] result = {};

		PluginDescriptor descriptor = null;
		try {
			descriptor = PluginDescriptor.fromFile(pluginLocation);
		} catch (Exception e) {
			getLog().warn("Error while getting manifest of " + pluginLocation,
					e);
		}

		// We resolve the manifest dependencies
		if (descriptor != null) {
			createAllDependencies(descriptor, pluginDir);
		} else {
			getLog().warn("No manifest found for '" + pluginLocation + "'");
		}

		return result;
	}

	private void createDependency(Bundle bundle, File path) {
		String artifactId = bundle.getArtifactId();
		String groupId = bundle.getGroupId();
		String scope = null;
		String systemPath = null;
		String type = null;
		String version = null;

		// resolve the version
		if (!bundle.getVersions().isEmpty()) {
			version = bundle.getVersions().get(bundle.getVersions().size() - 1)
					.toString();
		} else if (path != null) {
			// we try to extract the version from the file name
			Version vrs = Version.fromFileName(path.getName());
			if (vrs != null) {
				version = vrs.toString();
			}
		}

		if (version == null) {
			version = "1.0";
		}

		//The scope and the system path
		if (path != null) {
			scope = Artifact.SCOPE_SYSTEM;

			// The dependencies is a local file
			if (path.isDirectory()) {
				// we are looking for jar files
				File[] files = path.listFiles(new ExtensionFilter("jar"));
				
				// The easy case the folder contains only one jar
				if (files.length == 1) {
					String ext = extOf(files[0].getName()).trim();
					if (!ext.isEmpty()) {
						type = ext;
					}
					systemPath = files[0].getPath();
				} else if (files.length > 1) {
					//The folder has multiple jars. We declare each one 
					//as a dependency
					for (File jar : files) {
						Dependency dep = new Dependency();						
						dep.setGroupId(bundle.getFullName());
						dep.setArtifactId(jar.getName().substring(0, jar.getName().lastIndexOf(".")));
						if (version != null) {
							dep.setVersion(version);
						}
						dep.setScope(Artifact.SCOPE_SYSTEM);
						dep.setType("jar");
						dep.setSystemPath(jar.getPath());
						registerDependency(dep);
					}
				} else {
					//The bundle dependency could not been resolved
					getLog().warn("Unable to resolve dependency " + bundle);
					return;
				}
			} else {
				String ext = extOf(path.getName()).trim();
				if (!ext.isEmpty()) {
					type = ext;
				}
				systemPath = path.getPath();
			}
		} else {
			scope = Artifact.SCOPE_COMPILE;
		}

		Dependency dep = new Dependency();
		dep.setArtifactId(artifactId);
		dep.setGroupId(groupId);
		dep.setScope(scope);
		dep.setSystemPath(systemPath);
		dep.setType(type);
		dep.setVersion(version);

		// record the dependency
		registerDependency(dep);
	}

	private void registerDependency(Dependency dep) {
		project.getOriginalModel().addDependency(dep);
		project.getModel().addDependency(dep);
	}
	
	private void createAllDependencies(PluginDescriptor descriptor,
			File pluginsDir) throws MojoExecutionException,
			MojoFailureException {
		for (Bundle bundle : descriptor.getBundles()) {
			File file = resolvePluginFrom(pluginsDir, bundle);
			if (file == null) {
				file = resolvePluginFromWorkspace(bundle);
			}
			if (file != null && !resolvedBundles.contains(file)) {
				getLog().debug(
						"Resolved dependency for " + bundle + " = " + file);
				resolvedBundles.add(file);
				try {
					file = fetch(file);
				} catch (IOException e) {
					// Ignore
				}
				createDependency(bundle, file);
				resolvePluginDependencies(pluginsDir, file);
			} else if (file == null) {
				getLog().warn("Unable to resolve dependency " + bundle);
			}
			
			//We handle the special case of SWT bundle
			if ("org.eclipse.swt".equalsIgnoreCase(bundle.getFullName())) {
				addSWTCompanions(bundle, pluginsDir);
			}
		}
	}

	/**
	 * Copy the file into the temporary libraries location if one is set.
	 * 
	 * @param file
	 *            The file
	 */
	private File fetch(File file) throws IOException {
		File dir = getLibrairiesLocation();
		File newLocation = file;
		if (file != null && file.isFile() && dir != null
				&& (dir.isDirectory() || dir.mkdirs())) {
			newLocation = new File(dir, file.getName());
			FileUtils.copyFileIfModified(file, newLocation);
		}
		return newLocation;
	}

	private String extOf(String str) {
		String result = "";
		int idx = str.lastIndexOf(".");
		if (idx > -1) {
			result = str.substring(idx + 1);
		}
		return result;
	}

	/**
	 * SWT comes with a system specific library companion not declared into its
	 * manifest. So to build eclipse application which use SWT We must add this
	 * libraries into the classpath.
	 * 
	 * @param swtBundle
	 *            The bundle
	 * @param pluginDir
	 *            The plugins directory
	 * @return
	 */
	private void addSWTCompanions(final Bundle swtBundle, File pluginDir) {
		pluginDir.eachFile {
			if (it.name.startsWith(swtBundle.getFullName() + ".") && it.endsWith("jar") && !it.contains("source")) {
				createDependency(Bundle.fromFileName(it.getName()), it);
			}
		}
	}

}
